<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.0.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/wang-cheng/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/wang-cheng/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/wang-cheng/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/wang-cheng/images/logo.svg" color="#222">

<link rel="stylesheet" href="/wang-cheng/css/main.css">


<link rel="stylesheet" href="/wang-cheng/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"cheng-wang123.gitee.io","root":"/wang-cheng/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="RedisRedis线程模式，单线程为什么这么快Redis基于Reactor模式开发了网络事件处理器，这个处理器叫做文件事件处理器 file event handler。 这个文件事件处理器，它是单线程的，所以 Redis 才叫做单线程的模型，它采用IO多路复用机制来同时监听多个Socket，根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型，又可以跟内">
<meta property="og:type" content="article">
<meta property="og:title" content="面试题(八)">
<meta property="og:url" content="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/index.html">
<meta property="og:site_name" content="诚の博客">
<meta property="og:description" content="RedisRedis线程模式，单线程为什么这么快Redis基于Reactor模式开发了网络事件处理器，这个处理器叫做文件事件处理器 file event handler。 这个文件事件处理器，它是单线程的，所以 Redis 才叫做单线程的模型，它采用IO多路复用机制来同时监听多个Socket，根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型，又可以跟内">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/bgsave%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/AOF%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/AOF%E6%96%87%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D.png">
<meta property="article:published_time" content="2022-04-24T05:01:45.000Z">
<meta property="article:modified_time" content="2022-04-26T08:52:11.588Z">
<meta property="article:author" content="王诚">
<meta property="article:tag" content="面试题">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6.png">

<link rel="canonical" href="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>面试题(八) | 诚の博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/wang-cheng/atom.xml" title="诚の博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/wang-cheng/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">诚の博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">记录学习中的点点滴滴</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/wang-cheng/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/wang-cheng/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/wang-cheng/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/wang-cheng/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          面试题(八)
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-24 13:01:45" itemprop="dateCreated datePublished" datetime="2022-04-24T13:01:45+08:00">2022-04-24</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-26 16:52:11" itemprop="dateModified" datetime="2022-04-26T16:52:11+08:00">2022-04-26</time>
              </span>

          
            <span class="post-meta-item" title="阅读次数" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h1 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h1><h2 id="Redis线程模式，单线程为什么这么快"><a href="#Redis线程模式，单线程为什么这么快" class="headerlink" title="Redis线程模式，单线程为什么这么快"></a><strong>Redis线程模式，单线程为什么这么快</strong></h2><p>Redis基于Reactor模式开发了网络事件处理器，这个处理器叫做文件事件处理器 file event handler。</p>
<p>这个文件事件处理器，它是单线程的，所以 Redis 才叫做单线程的模型，<strong>它采用IO多路复用机制来同时监听多个Socket，根据Socket上的事件类型来选择对应的事件处理器来处理这个事件</strong>。可以实现高性能的网络通信模型，又可以跟内部其他单线程的模块进行对接，保证了 Redis 内部的线程模型的简单性。</p>
<p>文件事件处理器的结构包含4个部分：多个Socket、IO多路复用程序、文件事件分派器以及事件处理器（命令请求处理器、命令回复处理器、连接应答处理器等）。</p>
<p>多个 Socket 可能并发的产生不同的操作，每个操作对应不同的文件事件，但是IO多路复用程序会监听多个 Socket，会将 Socket 放入一个队列中排队，每次从队列中取出一个 Socket 给事件分派器，事件分派器把 Socket 给对应的事件处理器。</p>
<p>然后一个 Socket 的事件处理完之后，IO多路复用程序才会将队列中的下一个 Socket 给事件分派器。文件事件分派器会根据每个 Socket 当前产生的事件，来选择对应的事件处理器来处理。</p>
<p>什么时候适合使用多线程，什么时候适合使用单线程？</p>
<p>当一个请求需要处理很长时间或者操作很复杂，涉及到IO操作等，这个时候用多线程</p>
<p>当一个请求处理很快，但请求数量很多，这个时候用单线程</p>
<p>单线程快的原因：</p>
<ol>
<li>纯内存操作(内存操作速度很快)</li>
<li>核心是基于非阻塞的IO多路复用机制(因为主线程只会进行一些命令操作，不会进行IO操作，因为IO操作是往硬盘写数据，而这个功能是由RDB的fork线程来做的)</li>
<li>单线程反而避免了多线程的频繁上下文切换带来的性能问题</li>
</ol>
<h2 id="缓存雪崩、缓存穿透、缓存击穿、缓存降级"><a href="#缓存雪崩、缓存穿透、缓存击穿、缓存降级" class="headerlink" title="缓存雪崩、缓存穿透、缓存击穿、缓存降级"></a><strong>缓存雪崩、缓存穿透、缓存击穿、缓存降级</strong></h2><p>缓存雪崩是指缓存同一时间大面积的失效，或者是Redis发生宕机，Redis重启，所以后面的请求都会落到数据库上，造成数据库短时间内承受大量请求而崩掉。</p>
<p><strong>解决方案</strong>：</p>
<ul>
<li><p>缓存数据的过期时间设置随机，防止同一时间大量数据过期现象发生。</p>
</li>
<li><p>给每一个缓存数据增加相应的缓存标记，记录缓存是否失效，如果缓存标记失效，则更新数据缓存。(比较消耗内存，需要一直监控，但它会很快将数据补足，防止请求打到数据库上)</p>
</li>
<li><p>缓存预热(Redis重启之前，写一个接口，将热点数据放到缓存中，做一个缓存预热)</p>
</li>
<li><p>互斥锁(查一个缓存，发现缓存失效了，那么我要去查数据库，这个时候我要加一个锁，查数据库的时候，我把缓存的这个键锁起来，查完数据库再把它放到缓存中，然后释放这个锁。这样可以避免大量的请求对同一个键进行大量操作)</p>
</li>
</ul>
<p>缓存穿透是指缓存和数据库中都没有的数据，导致所有的请求都落到数据库上，造成数据库短时间内承受大量请求而崩掉(这种场景一般来自黑客攻击。因为正常用户请求一次发现没有数据就一般不会再次请求；而且缓存和数据库中都没有的数据的场景也不多，当然秒杀系统也是有可能产生缓存穿透的)</p>
<p><strong>解决方案：</strong></p>
<ul>
<li><p>接口层增加校验，如用户鉴权校验，id做基础校验，id&lt;=0的直接拦截(参数校验，将不合理的参数拦截下来)</p>
</li>
<li><p>从缓存取不到的数据，在数据库中也没有取到，这时也可以将key-value对写为key-null，缓存有效时间可以设置短点，如30秒（设置太长会导致正常情况也没法使用）。这样可以防止攻击用户反复用同一个id暴力攻击</p>
</li>
<li><p>采用布隆过滤器，将所有可能存在的数据哈希到一个足够大的 bitmap 中，一个一定不存在的数据会被这个 bitmap 拦截掉(存在的数据可能会被拦截掉，所以这里有一个误差，误差的计算方式在算法笔记中有记录)，从而避免了对底层存储系统的查询压力</p>
</li>
</ul>
<p>缓存击穿是指缓存中没有但数据库中有的数据（一般是缓存时间到期），这时由于并发用户特别多，时读缓存没读到数据，又同时去数据库去取数据，引起数据库压力瞬间增大，造成过大压力。和缓存雪崩不同的是，缓存击穿指并发查同一条数据(针对同一条数据)，缓存雪崩是不同数据都过期了，很多数据都查不到从而查数据库。</p>
<p><strong>解决方案</strong></p>
<ul>
<li><p>设置热点数据永远不过期。</p>
</li>
<li><p>加互斥锁</p>
</li>
</ul>
<p>缓存降级是指缓存失效或缓存服务器挂掉的情况下，不去访问数据库，直接返回默认数据或访问服务的内存数据。</p>
<p>在项目实战中通常会将部分热点数据缓存到服务的内存中，这样一旦缓存出现异常，可以直接使用服务的内存数据，从而避免数据库遭受巨大压力。</p>
<p>降级一般是有损的操作，所以尽量减少降级对于业务的影响程度。</p>
<h2 id="简述Redis事务实现-ACID"><a href="#简述Redis事务实现-ACID" class="headerlink" title="简述Redis事务实现 ACID"></a><strong>简述Redis事务实现 ACID</strong></h2><p>ACID指的是原子性、持久性、一致性和隔离性</p>
<p>1、事务开始</p>
<p>MULTI命令的执行，(执行这个命令之前可以执行watch命令来监控一个key)标识着一个事务的开始。MULTI命令会将客户端状态的 flags 属性中打开REDIS_MULTI 标识(标识打开意味着事务开始)来完成的。</p>
<p>2、命令入队<br>当一个客户端切换到事务状态之后，服务器会根据这个客户端发送来的命令来执行不同的操作。如果客户端发送的命令为MULTI、EXEC(执行事务中的命令)、WATCH、DISCARD(终止事务)中的一个(这四个命令并不会被当成事务命令去执行)，立即执行这个命令，否则将命令放入一个事务队列里面，然后向客户端返回 QUEUED 回复</p>
<ul>
<li><p>如果客户端发送的命令为 EXEC、DISCARD、WATCH、MULTI 四个命令的其中一个，那么服务器立即执行这个命令。</p>
</li>
<li><p>如果客户端发送的是四个命令以外的其他命令，那么服务器并不立即执行这个命令。<br>首先检查此命令的格式是否正确(检查语法)，如果不正确，服务器会在客户端状态（redisClient）的 flags 属性关闭 REDIS_MULTI 标识(事务结束)，并且返回错误信息给客户端。如果正确，将这个命令放入一个事务队列里面，然后向客户端返回 QUEUED 回复</p>
</li>
</ul>
<p>事务队列是按照FIFO的方式保存入队的命令</p>
<p>3、事务执行</p>
<p>客户端发送 EXEC 命令，服务器执行 EXEC 命令逻辑。</p>
<ul>
<li><p>如果客户端状态的 flags 属性不包含 REDIS_MULTI 标识，或者包含 REDIS_DIRTY_CAS 或者REDIS_DIRTY_EXEC 标识，那么就直接取消事务的执行</p>
</li>
<li><p>否则客户端处于事务状态（flags 有 REDIS_MULTI 标识），服务器会遍历客户端的事务队列，然后执行事务队列中的所有命令，最后将返回结果全部返回给客户端；</p>
</li>
</ul>
<p>redis 不支持事务回滚机制，但是它会检查每一个事务中的命令是否错误</p>
<p>Redis 事务不支持检查那些程序员自己逻辑错误。例如对 String 类型的数据库键执行对 HashMap 类型的操作！</p>
<ul>
<li><p>WATCH 命令是一个乐观锁，可以为 Redis 事务提供 check-and-set （CAS）行为。可以监控一个或多个键，一旦其中有一个键被修改（或删除），之后的事务就不会执行，监控一直持续到EXEC命令。</p>
</li>
<li><p>MULTI命令用于开启一个事务，它总是返回OK。MULTI执行之后，客户端可以继续向服务器发送任意多条命令，这些命令不会立即被执行，而是被放到一个队列中，当EXEC命令被调用时，所有队列中的命令才会被执行。</p>
</li>
<li><p>EXEC：执行所有事务块内的命令。返回事务块内所有命令的返回值，按命令执行的先后顺序排列。当操作被打断时，返回空值 nil 。</p>
</li>
<li><p>通过调用DISCARD，客户端可以清空事务队列，并放弃执行事务， 并且客户端会从事务状态中退出。</p>
</li>
<li><p>UNWATCH命令可以取消watch对所有key的监控</p>
</li>
</ul>
<h2 id="Redis集群方案"><a href="#Redis集群方案" class="headerlink" title="Redis集群方案"></a><strong>Redis集群方案</strong></h2><p>主从</p>
<p><strong>哨兵模式：</strong>(基于主从模式)</p>
<p>sentinel，哨兵是 redis 集群中非常重要的一个组件，主要有以下功能：</p>
<ul>
<li><p>集群监控：负责监控 redis master 和 slave 进程是否正常工作。</p>
</li>
<li><p>消息通知：如果某个 redis 实例有故障，那么哨兵负责发送消息作为报警通知给管理员。</p>
</li>
<li><p>故障转移：如果 master node 挂掉了，会自动转移到 slave node 上。</p>
</li>
<li><p>配置中心：如果故障转移发生了，通知 client 客户端新的 master 地址。</p>
</li>
</ul>
<p>哨兵用于实现 redis 集群的高可用，本身也是分布式的，作为一个哨兵集群去运行，互相协同工作。</p>
<ul>
<li><p>故障转移时，判断一个 master node 是否宕机了，需要大部分的哨兵都同意才行，涉及到了分布式选举</p>
</li>
<li><p>即使部分哨兵节点挂掉了，哨兵集群还是能正常工作的</p>
</li>
<li><p>哨兵通常需要 3 个实例，来保证自己的健壮性</p>
</li>
<li><p>哨兵 + redis 主从的部署架构，是不保证数据零丢失的，只能保证 redis 集群的高可用性</p>
</li>
<li><p>对于哨兵 + redis 主从这种复杂的部署架构，尽量在测试环境和生产环境，都进行充足的测试和演练。</p>
</li>
</ul>
<h2 id="redis-主从复制的核心原理"><a href="#redis-主从复制的核心原理" class="headerlink" title="redis 主从复制的核心原理"></a><strong>redis 主从复制的核心原理</strong></h2><p>主节点可以进行读写操作、从节点只可以进行读操作，从节点的数据都是从主节点拷贝来的，通过一些机制来进行拷贝。</p>
<p>通过执行slaveof命令或设置slaveof选项，让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作，当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的，并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库，而一个从数据库只能拥有一个主数据库。</p>
<p>全量复制：</p>
<p>（1）主节点通过bgsave命令fork子进程进行RDB持久化，该过程是非常消耗CPU、内存(页表复制)、硬盘IO的</p>
<p>（2）主节点通过网络将RDB文件发送给从节点，对主从节点的带宽都会带来很大的消耗</p>
<p>（3）从节点清空老数据、载入新RDB文件的过程是阻塞的，无法响应客户端的命令；如果从节点执行bgrewriteaof，也会带来额外的消耗</p>
<p>部分复制：</p>
<ol>
<li><p>复制偏移量：执行复制的双方，主从节点，分别会维护一个复制偏移量offset</p>
</li>
<li><p>复制积压缓冲区：主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区，当主从节点offset的差距过大超过缓冲区长度时，将无法执行部分复制，只能执行全量复制。</p>
</li>
<li><p>服务器运行ID(runid)：每个Redis节点，都有其运行ID，运行ID由节点在启动时自动生成，主节点会将自己的运行ID发送给从节点，从节点会将主节点的运行ID存起来。 从节点Redis断开重连的时候，就是根据运行ID来判断同步的进度</p>
<ul>
<li><p>如果从节点保存的runid与主节点现在的runid相同，说明主从节点之前同步过，主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况)；</p>
</li>
<li><p>如果从节点保存的runid与主节点现在的runid不同，说明从节点在断线前同步的Redis节点并不是当前的主节点，只能进行全量复制</p>
</li>
</ul>
</li>
</ol>
<p>过程原理：</p>
<img src="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6.png" class title="主从复制">
<h2 id="一个字符串类型的值能存储最大容量是多少？"><a href="#一个字符串类型的值能存储最大容量是多少？" class="headerlink" title="一个字符串类型的值能存储最大容量是多少？"></a><strong>一个字符串类型的值能存储最大容量是多少？</strong></h2><p>查询官方文档（<a target="_blank" rel="noopener" href="https://redis.io/topics/data-types）">https://redis.io/topics/data-types）</a></p>
<p>可以看到String类型的value值最多支持的长度为512M，所以正确的答案是512M。</p>
<h2 id="能说一下Redis每种数据结构的使用场景吗？"><a href="#能说一下Redis每种数据结构的使用场景吗？" class="headerlink" title="能说一下Redis每种数据结构的使用场景吗？"></a><strong>能说一下Redis每种数据结构的使用场景吗？</strong></h2><h3 id="（1）String的使用场景"><a href="#（1）String的使用场景" class="headerlink" title="（1）String的使用场景"></a><strong>（1）String的使用场景</strong></h3><pre><code>字符串类型的使用场景：信息缓存、计数器、分布式锁等等。
</code></pre><p><strong>实战场景1：记录每一个用户的访问次数，或者记录每一个商品的浏览次数</strong></p>
<p>方案：</p>
<p>常用键名：userid:pageview 或者 pageview:userid，如果一个用户的id为123，那对应的redis key就为pageview:123，value就为用户的访问次数，增加次数可以使用命令：incr。</p>
<p>使用理由：每一个用户访问次数或者商品浏览次数的修改是很频繁的，如果使用mysql这种文件系统频繁修改会造成mysql压力，效率也低。而使用redis的好处有二：使用内存，很快；单线程，所以无竞争，数据不会被改乱。</p>
<p><strong>实战场景2：缓存频繁读取，但是不常修改的信息，如用户信息，视频信息</strong></p>
<p>方案：</p>
<p>业务逻辑上：先从redis读取，有值就从redis读取，没有则从mysql读取，并写一份到redis中作为缓存，注意要设置过期时间。</p>
<p>键值设计上：</p>
<p>直接将用户一条mysql记录做序列化(通常序列化为json)作为值，userInfo:userid 作为key，键名如：userInfo:123，value存储对应用户信息的json串。如 key为：”user:id:name:1”,  value为”{“name”:”leijia”,”age”:18}”。</p>
<p><strong>实战场景3：限定某个ip特定时间内的访问次数</strong></p>
<p>方案：</p>
<p>用key记录IP，value记录访问次数，同时key的过期时间设置为60秒，如果key过期了则重新设置，否则进行判断，当一分钟内访问超过100次，则禁止访问。</p>
<p><strong>实战场景4:分布式session</strong></p>
<p>我们知道session是以文件的形式保存在服务器中的；如果你的应用做了负载均衡，将网站的项目放在多个服务器上，当用户在服务器A上进行登陆，session文件会写在A服务器；当用户跳转页面时，请求被分配到B服务器上的时候，就找不到这个session文件，用户就要重新登陆。</p>
<p>如果想要多个服务器共享一个session，可以将session存放在redis中，redis可以独立于所有负载均衡服务器，也可以放在其中一台负载均衡服务器上；但是所有应用所在的服务器连接的都是同一个redis服务器。</p>
<h3 id="（2）Hash的使用场景"><a href="#（2）Hash的使用场景" class="headerlink" title="（2）Hash的使用场景"></a><strong>（2）Hash的使用场景</strong></h3><p>以购物车为例子，用户id设置为key，那么购物车里所有的商品就是用户key对应的值了，每个商品有id和购买数量，对应hash的结构就是商品id为field，商品数量为value。</p>
<h3 id="（3）List的使用场景"><a href="#（3）List的使用场景" class="headerlink" title="（3）List的使用场景"></a><strong>（3）List的使用场景</strong></h3><p>列表本质是一个有序的，元素可重复的队列。</p>
<p><strong>实战场景：定时排行榜</strong></p>
<p>list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中，如QQ音乐内地排行榜，每周计算一次存储再list类型中，访问接口时通过page和size分页转化成lrange命令获取排行榜数据。</p>
<p>但是，并不是所有的排行榜都能用list类型实现，只有定时计算的排行榜才适合使用list类型存储，与定时计算的排行榜相对应的是实时计算的排行榜，list类型不能支持实时计算的排行榜，下面介绍有序集合sorted set的应用场景时会详细介绍实时计算的排行榜的实现。</p>
<h3 id="（4）Set的使用场景"><a href="#（4）Set的使用场景" class="headerlink" title="（4）Set的使用场景"></a><strong>（4）Set的使用场景</strong></h3><p>集合的特点是无序性和确定性（不重复）。</p>
<p><strong>实战场景：收藏夹</strong></p>
<p>例如QQ音乐中如果你喜欢一首歌，点个『喜欢』就会将歌曲放到个人收藏夹中，每一个用户做一个收藏的集合，每个收藏的集合存放用户收藏过的歌曲id。</p>
<p>key为用户id，value为歌曲id的集合。</p>
<h3 id="（5）Sorted-Set的使用场景"><a href="#（5）Sorted-Set的使用场景" class="headerlink" title="（5）Sorted Set的使用场景"></a><strong>（5）Sorted Set的使用场景</strong></h3><p>有序集合的特点是有序，无重复值。与set不同的是sorted set每个元素都会关联一个score属性，redis正是通过score来为集合中的成员进行从小到大的排序。</p>
<p><strong>实战场景：实时排行榜</strong></p>
<p>QQ音乐中有多种实时榜单，比如飙升榜、热歌榜、新歌榜，可以用redis key存储榜单类型，score为点击量，value为歌曲id，用户每点击一首歌曲会更新redis数据，sorted set会依据score即点击量将歌曲id排序。</p>
<h2 id="Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？"><a href="#Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？" class="headerlink" title="Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？"></a><strong>Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？</strong></h2><p>什么是持久化？</p>
<p>持久化（Persistence），即把数据（如内存中的对象）保存到可永久保存的存储设备中（如磁盘）。持久化的主要应用是将内存中的对象存储在数据库中，或者存储在磁盘文件中、XML数据文件中等等。</p>
<h3 id="Redis如何实现持久化？"><a href="#Redis如何实现持久化？" class="headerlink" title="Redis如何实现持久化？"></a><strong>Redis如何实现持久化？</strong></h3><p>Redis官方提供了不同级别的持久化方式：</p>
<ul>
<li><p>RDB持久化：能够在指定的时间间隔能对你的数据进行快照存储。</p>
</li>
<li><p>AOF持久化：记录每次对服务器写的操作，当服务器重启的时候会重新执行这些命令来恢复原始的数据，AOF命令以redis协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写，使得AOF文件的体积不至于过大。</p>
</li>
<li><p>不使用持久化：如果你只希望你的数据在服务器运行的时候存在，你也可以选择不使用任何持久化方式。</p>
</li>
<li><p>同时开启RDB和AOF：你也可以同时开启两种持久化方式，在这种情况下当redis重启的时候会优先载入AOF文件来恢复原始的数据，因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。</p>
</li>
</ul>
<h3 id="RDB持久化"><a href="#RDB持久化" class="headerlink" title="RDB持久化"></a><strong>RDB持久化</strong></h3><p>RDB(Redis Database)持久化是把当前内存数据生成快照保存到硬盘的过程，触发RDB持久化过程分为手动触发和自动触发。</p>
<p>（1）手动触发</p>
<p>手动触发对应save命令，会阻塞当前Redis服务器，直到RDB过程完成为止，对于内存比较大的实例会造成长时间阻塞，线上环境不建议使用。</p>
<p>（2）自动触发</p>
<p>自动触发对应bgsave命令，Redis进程执行fork操作创建子进程，RDB持久化过程由子进程负责，完成后自动结束。阻塞只发生在fork阶段，一般时间很短。</p>
<p>在redis.conf配置文件中可以配置：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">save <span class="tag">&lt;<span class="name">seconds</span>&gt;</span> <span class="tag">&lt;<span class="name">changes</span>&gt;</span></span><br></pre></td></tr></table></figure><br>表示xx秒内数据修改xx次时自动触发bgsave。如果想关闭自动触发，可以在save命令后面加一个空串，即：<br><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">save</span> <span class="string">&quot;&quot;</span></span><br></pre></td></tr></table></figure></p>
<p>还有其他常见可以触发bgsave，如：</p>
<ul>
<li><p>如果从节点执行全量复制操作，主节点自动执行bgsave生成RDB文件并发送给从节点。</p>
</li>
<li><p>默认情况下执行shutdown命令时，如果没有开启AOF持久化功能则 自动执行bgsave。</p>
</li>
</ul>
<p><strong>bgsave工作机制</strong></p>
<img src="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/bgsave%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6.png" class title="bgsave工作机制">
<p>（1）执行bgsave命令，Redis父进程判断当前是否存在正在执行的子进 程，如RDB/AOF子进程，如果存在，bgsave命令直接返回。</p>
<p>（2）父进程执行fork操作创建子进程，fork操作过程中父进程会阻塞，通 过info stats命令查看latest_fork_usec选项，可以获取最近一个fork操作的耗时，单位为微秒</p>
<p>（3）父进程fork完成后，bgsave命令返回“Background saving started”信息并不再阻塞父进程，可以继续响应其他命令。</p>
<p>（4）子进程创建RDB文件，根据父进程内存生成临时快照文件，完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的 时间，对应info统计的rdb_last_save_time选项。</p>
<p>（5）进程发送信号给父进程表示完成，父进程更新统计信息，具体见 info Persistence下的rdb_*相关选项。</p>
<p>— RDB持久化完 —</p>
<h3 id="AOF持久化"><a href="#AOF持久化" class="headerlink" title="AOF持久化"></a><strong>AOF持久化</strong></h3><p>AOF（append only file）持久化：以独立日志的方式记录每次写命令， 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性，目前已经是Redis持久化的主流方式。</p>
<p>AOF持久化工作机制</p>
<p>开启AOF功能需要配置：appendonly yes，默认不开启。</p>
<p>AOF文件名 通过appendfilename配置设置，默认文件名是appendonly.aof。保存路径同 RDB持久化方式一致，通过dir配置指定。</p>
<p>AOF的工作流程操作：命令写入 （append）、文件同步（sync）、文件重写（rewrite）、重启加载 （load）。</p>
<img src="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/AOF%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B.png" class title="AOF工作流程">
<ol>
<li><p>所有的写入命令会追加到aof_buf（缓冲区）中。</p>
</li>
<li><p>AOF缓冲区根据对应的策略向硬盘做同步操作。</p>
</li>
</ol>
<p>AOF为什么把命令追加到aof_buf中？Redis使用单线程响应命令，如果每次写AOF文件命令都直接追加到硬盘，那么性能完全取决于当前硬盘负载。先写入缓冲区aof_buf中，还有另一个好处，Redis可以提供多种缓冲区同步硬盘的策略，在性能和安全性方面做出平衡。</p>
<ol>
<li><p>随着AOF文件越来越大，需要定期对AOF文件进行重写，达到压缩的目的。</p>
</li>
<li><p>当Redis服务器重启时，可以加载AOF文件进行数据恢复。</p>
</li>
</ol>
<p><strong>AOF重写（rewrite）机制</strong></p>
<p>重写的目的：</p>
<ul>
<li>减小AOF文件占用空间；</li>
<li>更小的AOF 文件可以更快地被Redis加载恢复。</li>
</ul>
<p>AOF重写可以分为手动触发和自动触发：</p>
<ul>
<li>手动触发：直接调用bgrewriteaof命令。</li>
<li>自动触发：根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。</li>
</ul>
<p>auto-aof-rewrite-min-size：表示运行AOF重写时文件最小体积，默认 为64MB。</p>
<p>auto-aof-rewrite-percentage：代表当前AOF文件空间 （aof_current_size）和上一次重写后AOF文件空间（aof_base_size）的比值。</p>
<p>自动触发时机</p>
<p>当aof_current_size&gt;auto-aof-rewrite-minsize 并且（aof_current_size-aof_base_size）/aof_base_size&gt;=auto-aof-rewritepercentage。</p>
<p>其中aof_current_size和aof_base_size可以在info Persistence统计信息中查看。</p>
<img src="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/AOF%E6%96%87%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D.png" class title="AOF文件数据恢复">
<p>数据恢复流程说明：</p>
<p>（1）AOF持久化开启且存在AOF文件时，优先加载AOF文件。</p>
<p>（2）AOF关闭或者AOF文件不存在时，加载RDB文件。</p>
<p>（3）加载AOF/RDB文件成功后，Redis启动成功。</p>
<p>（4）AOF/RDB文件存在错误时，Redis启动失败并打印错误信息。</p>
<p>— AOF持久化完 —</p>
<h3 id="RDB和AOF的优缺点"><a href="#RDB和AOF的优缺点" class="headerlink" title="RDB和AOF的优缺点"></a><strong>RDB和AOF的优缺点</strong></h3><p>RDB优点</p>
<ul>
<li>RDB 是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。</li>
<li>RDB 是一个紧凑的单一文件,很方便传送到另一个远端数据中心，非常适用于灾难恢复。</li>
<li>RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做，父进程不需要再做其他 IO 操作，所以 RDB 持久化方式可以最大化 Redis 的性能。</li>
<li>与AOF相比,在恢复大的数据集的时候，RDB 方式会更快一些。</li>
</ul>
<p>AOF优点</p>
<ul>
<li>你可以使用不同的 fsync(fsync函数同步内存中所有已修改的文件数据到储存设备) 策略：无 fsync、每秒 fsync 、每次写的时候 fsync .使用默认的每秒 fsync 策略, Redis 的性能依然很好( fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障，你最多丢失1秒的数据。</li>
<li>AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满，写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题。</li>
<li>Redis 可以在 AOF 文件体积变得过大时，自动地在后台对 AOF 进行重写：重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的，因为 Redis 在创建新 AOF 文件的过程中，会继续将命令追加到现有的 AOF 文件里面，即使重写过程中发生停机，现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕，Redis 就会从旧 AOF 文件切换到新 AOF 文件，并开始对新 AOF 文件进行追加操作。</li>
<li>AOF 文件有序地保存了对数据库执行的所有写入操作， 这些写入操作以 Redis 协议的格式保存， 因此 AOF 文件的内容非常容易被人读懂， 对文件进行分析（parse）也很轻松。导出（export） AOF 文件也非常简单：举个例子， 如果你不小心执行了 FLUSHALL 命令， 但只要 AOF 文件未被重写， 那么只要停止服务器， 移除 AOF 文件末尾的 FLUSHALL 命令， 并重启 Redis ， 就可以将数据集恢复到 FLUSHALL 执行之前的状态。</li>
</ul>
<p>RDB缺点</p>
<ul>
<li>Redis 要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,你可能会丢失几分钟的数据。</li>
<li>RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候, fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。</li>
</ul>
<p>AOF缺点</p>
<ul>
<li>对于相同的数据集来说，AOF 文件的体积通常要大于 RDB 文件的体积。</li>
<li>数据恢复（load）时AOF比RDB慢，通常RDB 可以提供更有保证的最大延迟时间。</li>
</ul>
<p>RDB和AOF简单对比总结</p>
<p>RDB优点：</p>
<ul>
<li>RDB 是紧凑的二进制文件，比较适合备份，全量复制等场景</li>
<li>RDB 恢复数据远快于 AOF</li>
</ul>
<p>RDB缺点：</p>
<ul>
<li>RDB 无法实现实时或者秒级持久化；</li>
<li>新老版本无法兼容 RDB 格式。</li>
</ul>
<p>AOF优点：</p>
<ul>
<li>可以更好地保护数据不丢失；</li>
<li>appen-only 模式写入性能比较高；</li>
<li>适合做灾难性的误删除紧急恢复。</li>
</ul>
<p>AOF缺点：</p>
<ul>
<li>对于同一份文件，AOF 文件要比 RDB 快照大；</li>
<li>AOF 开启后，会对写的 QPS(每秒查询率（QPS，Queries-per-second）) 有所影响，相对于 RDB 来说 写 QPS 要下降；</li>
<li>数据库恢复比较慢， 不合适做冷备。</li>
</ul>
<hr>
<h2 id="Redis的内存淘汰机制"><a href="#Redis的内存淘汰机制" class="headerlink" title="Redis的内存淘汰机制"></a><strong>Redis的内存淘汰机制</strong></h2><p>Redis内存淘汰策略是指当缓存内存不足时，通过淘汰旧数据处理新加入数据选择的策略。</p>
<p><strong>如何配置最大内存？</strong></p>
<p>（1）通过配置文件配置</p>
<p>修改redis.conf配置文件</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">maxmemory</span> <span class="number">1024</span>mb //设置Redis最大占用内存大小为<span class="number">1024</span>M</span><br></pre></td></tr></table></figure>
<p>注意：maxmemory默认配置为0，在64位操作系统下redis最大内存为操作系统剩余内存，在32位操作系统下redis最大内存为3GB。</p>
<p>（2）通过动态命令配置</p>
<p>Redis支持运行时通过命令动态修改内存大小：</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">6379</span>&gt; config set maxmemory <span class="number">200</span>mb //设置Redis最大占用内存大小为<span class="number">200</span>M</span><br><span class="line"><span class="attribute">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">6379</span>&gt; config get maxmemory //获取设置的Redis能使用的最大内存大小</span><br><span class="line"><span class="attribute">1</span>) <span class="string">&quot;maxmemory&quot;</span></span><br><span class="line"><span class="attribute">2</span>) <span class="string">&quot;209715200&quot;</span></span><br></pre></td></tr></table></figure>
<p>淘汰策略的分类</p>
<p>Redis最大占用内存用完之后，如果继续添加数据，如何处理这种情况呢？实际上Redis官方已经定义了八种策略来处理这种情况：</p>
<p><strong>noeviction</strong></p>
<p>默认策略，对于写请求直接返回错误，不进行淘汰。</p>
<p><strong>allkeys-lru</strong></p>
<p>lru(less recently used), 最近最少使用。从所有的key中使用近似LRU算法进行淘汰。</p>
<p><strong>volatile-lru</strong></p>
<p>lru(less recently used), 最近最少使用。从设置了过期时间的key中使用近似LRU算法进行淘汰。</p>
<p><strong>allkeys-random</strong></p>
<p>从所有的key中随机淘汰。</p>
<p><strong>volatile-random</strong></p>
<p>从设置了过期时间的key中随机淘汰。</p>
<p><strong>volatile-ttl</strong></p>
<p>ttl(time to live)，在设置了过期时间的key中根据key的过期时间进行淘汰，越早过期的越优先被淘汰。</p>
<p><strong>allkeys-lfu</strong></p>
<p>lfu(Least Frequently Used)，最少使用频率。从所有的key中使用近似LFU算法进行淘汰。从Redis4.0开始支持。</p>
<p><strong>volatile-lfu</strong></p>
<p>lfu(Least Frequently Used)，最少使用频率。从设置了过期时间的key中使用近似LFU算法进行淘汰。从Redis4.0开始支持。</p>
<p>注意：当使用volatile-lru、volatile-random、volatile-ttl这三种策略时，如果没有设置过期的key可以被淘汰，则和noeviction一样返回错误。</p>
<h3 id="LRU算法"><a href="#LRU算法" class="headerlink" title="LRU算法"></a><strong>LRU算法</strong></h3><p>LRU(Least Recently Used)，即最近最少使用，是一种缓存置换算法。在使用内存作为缓存的时候，缓存的大小一般是固定的。当缓存被占满，这个时候继续往缓存里面添加数据，就需要淘汰一部分老的数据，释放内存空间用来存储新的数据。这个时候就可以使用LRU算法了。其核心思想是：如果一个数据在最近一段时间没有被用到，那么将来被使用到的可能性也很小，所以就可以被淘汰掉。</p>
<h3 id="LRU在Redis中的实现"><a href="#LRU在Redis中的实现" class="headerlink" title="LRU在Redis中的实现"></a><strong>LRU在Redis中的实现</strong></h3><p>Redis使用的是近似LRU算法，它跟常规的LRU算法还不太一样。近似LRU算法通过随机采样法淘汰数据，每次随机出5个（默认）key，从里面淘汰掉最近最少使用的key。</p>
<p>可以通过maxmemory-samples参数修改采样数量， 如：maxmemory-samples 10</p>
<p>maxmenory-samples配置的越大，淘汰的结果越接近于严格的LRU算法，但因此耗费的CPU也很高。</p>
<p>Redis为了实现近似LRU算法，给每个key增加了一个额外增加了一个24bit的字段，用来存储该key最后一次被访问的时间。</p>
<h3 id="Redis3-0对近似LRU的优化"><a href="#Redis3-0对近似LRU的优化" class="headerlink" title="Redis3.0对近似LRU的优化"></a><strong>Redis3.0对近似LRU的优化</strong></h3><p>Redis3.0对近似LRU算法进行了一些优化。新算法会维护一个候选池（大小为16），池中的数据根据访问时间进行排序，第一次随机选取的key都会放入池中，随后每次随机选取的key只有在访问时间小于池中最小的时间才会放入池中，直到候选池被放满。当放满后，如果有新的key需要放入，则将池中最后访问时间最大（最近被访问）的移除。</p>
<p>当需要淘汰的时候，则直接从池中选取最近访问时间最小（最久没被访问）的key淘汰掉就行。</p>
<h3 id="LFU算法"><a href="#LFU算法" class="headerlink" title="LFU算法"></a><strong>LFU算法</strong></h3><p>LFU(Least Frequently Used)，是Redis4.0新加的一种淘汰策略，它的核心思想是根据key的最近被访问的频率进行淘汰，很少被访问的优先被淘汰，被访问的多的则被留下来。</p>
<p>LFU算法能更好的表示一个key被访问的热度。假如你使用的是LRU算法，一个key很久没有被访问到，只刚刚是偶尔被访问了一次，那么它就被认为是热点数据，不会被淘汰，而有些key将来是很有可能被访问到的则被淘汰了。如果使用LFU算法则不会出现这种情况，因为使用一次并不会使一个key成为热点数据。</p>
<h2 id="Redis有事务机制吗？"><a href="#Redis有事务机制吗？" class="headerlink" title="Redis有事务机制吗？"></a><strong>Redis有事务机制吗？</strong></h2><p>有事务机制。Redis事务生命周期：</p>
<ul>
<li>开启事务：使用MULTI开启一个事务</li>
<li>命令入队列：每次操作的命令都会加入到一个队列中，但命令此时不会真正被执行</li>
<li>提交事务：使用EXEC命令提交事务，开始顺序执行队列中的命令</li>
</ul>
<h2 id="Redis事务到底是不是原子性的？"><a href="#Redis事务到底是不是原子性的？" class="headerlink" title="Redis事务到底是不是原子性的？"></a><strong>Redis事务到底是不是原子性的？</strong></h2><p>先看关系型数据库ACID 中关于原子性的定义：</p>
<p><strong>原子性：</strong>一个事务(transaction)中的所有操作，要么全部完成，要么全部不完成，不会结束在中间某个环节。事务在执行过程中发生错误，会被恢复(Rollback)到事务开始前的状态，就像这个事务从来没有执行过一样。</p>
<p>官方文档对事务的定义：</p>
<ul>
<li>事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。</li>
<li>事务是一个原子操作：事务中的命令要么全部被执行，要么全部都不执行。EXEC 命令负责触发并执行事务中的所有命令：如果客户端在使用 MULTI 开启了一个事务之后，却因为断线而没有成功执行 EXEC ，那么事务中的所有命令都不会被执行。另一方面，如果客户端成功在开启事务之后执行 EXEC ，那么事务中的所有命令都会被执行。</li>
</ul>
<p>官方认为Redis事务是一个原子操作，这是站在执行与否的角度考虑的。但是从ACID原子性定义来看，严格意义上讲Redis事务是非原子型的，因为在命令顺序执行过程中，一旦发生命令执行错误Redis是不会停止执行然后回滚数据。</p>
<h2 id="Redis为什么不支持回滚（roll-back）？"><a href="#Redis为什么不支持回滚（roll-back）？" class="headerlink" title="Redis为什么不支持回滚（roll back）？"></a><strong>Redis为什么不支持回滚（roll back）？</strong></h2><p>在事务运行期间虽然Redis命令可能会执行失败，但是Redis依然会执行事务内剩余的命令而不会执行回滚操作。如果你熟悉mysql关系型数据库事务，你会对此非常疑惑，Redis官方的理由如下：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">只有当被调用的Redis命令有语法错误时，这条命令才会执行失败（在将这个命令放入事务队列期间，Redis能够发现此类问题），或者对某个键执行不符合其数据类型的操作：实际上，这就意味着只有程序错误才会导致Redis命令执行失败，这种错误很有可能在程序开发期间发现，一般很少在生产环境发现。支持事务回滚能力会导致设计复杂，这与Redis的初衷相违背，Redis的设计目标是功能简化及确保更快的运行速度。</span><br></pre></td></tr></table></figure>
<p>对于官方的这种理由有一个普遍的反对观点：程序有bug怎么办？但其实回归不能解决程序的bug，比如某位粗心的程序员计划更新键A，实际上最后更新了键B，回滚机制是没法解决这种人为错误的。正因为这种人为的错误不太可能进入生产系统，所以官方在设计Redis时选用更加简单和快速的方法，没有实现回滚的机制。</p>
<h2 id="什么是Redis主从复制？"><a href="#什么是Redis主从复制？" class="headerlink" title="什么是Redis主从复制？"></a><strong>什么是Redis主从复制？</strong></h2><p>主从复制，是指将一台Redis服务器的数据，复制到其他的Redis服务器。前者称为主节点(master)，后者称为从节点(slave)；数据的复制是单向的，只能由主节点到从节点。</p>
<p>主从复制的作用</p>
<ul>
<li><p>数据冗余：主从复制实现了数据的热备份，是持久化之外的一种数据冗余方式。</p>
</li>
<li><p>故障恢复：当主节点出现问题时，可以由从节点提供服务，实现快速的故障恢复；实际上是一种服务的冗余。</p>
</li>
<li><p>负载均衡：在主从复制的基础上，配合读写分离，可以由主节点提供写服务，由从节点提<br>供读服务，分担服务器负载；尤其是在写少读多的场景下，通过多个从节点分担读负载，可以大大提高Redis服务器的并发量。</p>
</li>
<li><p>高可用基石：主从复制还是哨兵和集群能够实施的基础，因此说主从复制是Redis高可用的基础。</p>
</li>
</ul>
<p>主从复制实现原理</p>
<p>主从复制过程主要可以分为3个阶段：连接建立阶段、数据同步阶段、命令传播阶段。</p>
<p>连接建立阶段</p>
<p>该阶段的主要作用是在主从节点之间建立连接，为数据同步做好准备。</p>
<p>步骤1：保存主节点信息</p>
<p>slaveof命令是异步的，在从节点上执行slaveof命令，从节点立即向客户端返回ok，从节点服务器内部维护了两个字段，即masterhost和masterport字段，用于存储主节点的ip和port信息。</p>
<p>步骤2：建立socket连接</p>
<p>从节点每秒1次调用复制定时函数replicationCron()，如果发现了有主节点可以连接，便会根据主节点的ip和port，创建socket连接。</p>
<p>从节点为该socket建立一个专门处理复制工作的文件事件处理器，负责后续的复制工作，如接收RDB文件、接收命令传播等。</p>
<p>主节点接收到从节点的socket连接后（即accept之后），为该socket创建相应的客户端状态，并将从节点看做是连接到主节点的一个客户端，后面的步骤会以从节点向主节点发送命令请求的形式来进行。</p>
<p>步骤3：发送ping命令</p>
<p>从节点成为主节点的客户端之后，发送ping命令进行首次请求，目的是：检查socket连接是否可用，以及主节点当前是否能够处理请求。</p>
<p>从节点发送ping命令后，可能出现3种情况：</p>
<p>（1）返回pong：说明socket连接正常，且主节点当前可以处理请求，复制过程继续。</p>
<p>（2）超时：一定时间后从节点仍未收到主节点的回复，说明socket连接不可用，则从节点断开socket连接，并重连。</p>
<p>（3）返回pong以外的结果：如果主节点返回其他结果，如正在处理超时运行的脚本，说明主节点当前无法处理命令，则从节点断开socket连接，并重连。</p>
<p>步骤4：身份验证</p>
<p>如果从节点中设置了masterauth选项，则从节点需要向主节点进行身份验证；没有设置该选项，则不需要验证。从节点进行身份验证是通过向主节点发送auth命令进行的，auth命令的参数即为配置文件中的masterauth的值。</p>
<p>如果主节点设置密码的状态，与从节点masterauth的状态一致（一致是指都存在，且密码相同，或者都不存在），则身份验证通过，复制过程继续；如果不一致，则从节点断开socket连接，并重连。</p>
<p>步骤5：发送从节点端口信息</p>
<p>身份验证之后，从节点会向主节点发送其监听的端口号（前述例子中为6380），主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中；该端口信息除了在主节点中执行info Replication时显示以外，没有其他作用。</p>
<p>数据同步阶段</p>
<p>主从节点之间的连接建立以后，便可以开始进行数据同步，该阶段可以理解为从节点数据的初始化。具体执行的方式是：从节点向主节点发送psync命令（Redis2.8以前是sync命令），开始同步。</p>
<p>数据同步阶段是主从复制最核心的阶段，根据主从节点当前状态的不同，可以分为全量复制和部分复制，后面再讲解这两种复制方式以及psync命令的执行过程，这里不再详述。</p>
<p>命令传播阶段</p>
<p>数据同步阶段完成后，主从节点进入命令传播阶段；在这个阶段主节点将自己执行的写命令发送给从节点，从节点接收命令并执行，从而保证主从节点数据的一致性。</p>
<p>需要注意的是，命令传播是异步的过程，即主节点发送写命令后并不会等待从节点的回复；因此实际上主从节点之间很难保持实时的一致性，延迟在所难免。数据不一致的程度，与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。</p>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/wang-cheng/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/" rel="tag"># 面试题</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/wang-cheng/2022/04/20/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%88%B7%E9%A2%98/" rel="prev" title="计算机网络刷题">
      <i class="fa fa-chevron-left"></i> 计算机网络刷题
    </a></div>
      <div class="post-nav-item">
    <a href="/wang-cheng/2022/04/28/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E9%9D%A2%E8%AF%95%E9%A2%98/" rel="next" title="多线程面试题">
      多线程面试题 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#Redis"><span class="nav-number">1.</span> <span class="nav-text">Redis</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%BC%8F%EF%BC%8C%E5%8D%95%E7%BA%BF%E7%A8%8B%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%99%E4%B9%88%E5%BF%AB"><span class="nav-number">1.1.</span> <span class="nav-text">Redis线程模式，单线程为什么这么快</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9%E3%80%81%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F%E3%80%81%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF%E3%80%81%E7%BC%93%E5%AD%98%E9%99%8D%E7%BA%A7"><span class="nav-number">1.2.</span> <span class="nav-text">缓存雪崩、缓存穿透、缓存击穿、缓存降级</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%AE%80%E8%BF%B0Redis%E4%BA%8B%E5%8A%A1%E5%AE%9E%E7%8E%B0-ACID"><span class="nav-number">1.3.</span> <span class="nav-text">简述Redis事务实现 ACID</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E9%9B%86%E7%BE%A4%E6%96%B9%E6%A1%88"><span class="nav-number">1.4.</span> <span class="nav-text">Redis集群方案</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#redis-%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E7%9A%84%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86"><span class="nav-number">1.5.</span> <span class="nav-text">redis 主从复制的核心原理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%B1%BB%E5%9E%8B%E7%9A%84%E5%80%BC%E8%83%BD%E5%AD%98%E5%82%A8%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E6%98%AF%E5%A4%9A%E5%B0%91%EF%BC%9F"><span class="nav-number">1.6.</span> <span class="nav-text">一个字符串类型的值能存储最大容量是多少？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%83%BD%E8%AF%B4%E4%B8%80%E4%B8%8BRedis%E6%AF%8F%E7%A7%8D%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E5%90%97%EF%BC%9F"><span class="nav-number">1.7.</span> <span class="nav-text">能说一下Redis每种数据结构的使用场景吗？</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%EF%BC%881%EF%BC%89String%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="nav-number">1.7.1.</span> <span class="nav-text">（1）String的使用场景</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%EF%BC%882%EF%BC%89Hash%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="nav-number">1.7.2.</span> <span class="nav-text">（2）Hash的使用场景</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%EF%BC%883%EF%BC%89List%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="nav-number">1.7.3.</span> <span class="nav-text">（3）List的使用场景</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%EF%BC%884%EF%BC%89Set%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="nav-number">1.7.4.</span> <span class="nav-text">（4）Set的使用场景</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%EF%BC%885%EF%BC%89Sorted-Set%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="nav-number">1.7.5.</span> <span class="nav-text">（5）Sorted Set的使用场景</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E5%A6%82%E4%BD%95%E5%81%9A%E6%8C%81%E4%B9%85%E5%8C%96%E7%9A%84%EF%BC%9F%E8%83%BD%E8%AF%B4%E4%B8%80%E4%B8%8BRDB%E5%92%8CAOF%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E5%90%97%EF%BC%9F"><span class="nav-number">1.8.</span> <span class="nav-text">Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Redis%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E6%8C%81%E4%B9%85%E5%8C%96%EF%BC%9F"><span class="nav-number">1.8.1.</span> <span class="nav-text">Redis如何实现持久化？</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#RDB%E6%8C%81%E4%B9%85%E5%8C%96"><span class="nav-number">1.8.2.</span> <span class="nav-text">RDB持久化</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#AOF%E6%8C%81%E4%B9%85%E5%8C%96"><span class="nav-number">1.8.3.</span> <span class="nav-text">AOF持久化</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#RDB%E5%92%8CAOF%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9"><span class="nav-number">1.8.4.</span> <span class="nav-text">RDB和AOF的优缺点</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E7%9A%84%E5%86%85%E5%AD%98%E6%B7%98%E6%B1%B0%E6%9C%BA%E5%88%B6"><span class="nav-number">1.9.</span> <span class="nav-text">Redis的内存淘汰机制</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#LRU%E7%AE%97%E6%B3%95"><span class="nav-number">1.9.1.</span> <span class="nav-text">LRU算法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#LRU%E5%9C%A8Redis%E4%B8%AD%E7%9A%84%E5%AE%9E%E7%8E%B0"><span class="nav-number">1.9.2.</span> <span class="nav-text">LRU在Redis中的实现</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Redis3-0%E5%AF%B9%E8%BF%91%E4%BC%BCLRU%E7%9A%84%E4%BC%98%E5%8C%96"><span class="nav-number">1.9.3.</span> <span class="nav-text">Redis3.0对近似LRU的优化</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#LFU%E7%AE%97%E6%B3%95"><span class="nav-number">1.9.4.</span> <span class="nav-text">LFU算法</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E6%9C%89%E4%BA%8B%E5%8A%A1%E6%9C%BA%E5%88%B6%E5%90%97%EF%BC%9F"><span class="nav-number">1.10.</span> <span class="nav-text">Redis有事务机制吗？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E4%BA%8B%E5%8A%A1%E5%88%B0%E5%BA%95%E6%98%AF%E4%B8%8D%E6%98%AF%E5%8E%9F%E5%AD%90%E6%80%A7%E7%9A%84%EF%BC%9F"><span class="nav-number">1.11.</span> <span class="nav-text">Redis事务到底是不是原子性的？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E6%94%AF%E6%8C%81%E5%9B%9E%E6%BB%9A%EF%BC%88roll-back%EF%BC%89%EF%BC%9F"><span class="nav-number">1.12.</span> <span class="nav-text">Redis为什么不支持回滚（roll back）？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BB%80%E4%B9%88%E6%98%AFRedis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%EF%BC%9F"><span class="nav-number">1.13.</span> <span class="nav-text">什么是Redis主从复制？</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">王诚</p>
  <div class="site-description" itemprop="description">命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/wang-cheng/archives/">
        
          <span class="site-state-item-count">32</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/wang-cheng/tags/">
          
        <span class="site-state-item-count">12</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">王诚</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/wang-cheng/lib/anime.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.ui.min.js"></script>

<script src="/wang-cheng/js/utils.js"></script>

<script src="/wang-cheng/js/motion.js"></script>


<script src="/wang-cheng/js/schemes/pisces.js"></script>


<script src="/wang-cheng/js/next-boot.js"></script>




  















  

  

  

</body>
</html>
